﻿using System;//
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using System.IO;
using System.Diagnostics;


namespace PrototypeZGame
{
    public class World
    {
        public int fieldResolution;
        public Texture2D tileset;
        
        public SpriteBatch spriteBatch;
        public SpriteFont screenFont;
        public int screenWidth, screenHeight;
        public int fieldsDrawn;
        public Matrix globalTransformation;
        public Color[] codeToColor;
        public Terrain terrain;
        public Vector2 mouseWorldPosition;
        public Random rand;
        public int[,] pathFindingResult;
        public int DSR;
        public bool debugPathfinding;
        public Int64 time;
        public Int64 lastIterationTime = 0;
        public Int64 maxIterationTime = 10000;
        public int iteration = 0;
        public int takeBest = 10;


        public List<Actor> actors;
        public List<Unit> units;
        public List<Human> humans;
        public List<Zombie> zombies;


        public PlayerController pc;
        public List<AIController> aiControllers;
        public bool pause = false;
        public bool clear = false;

        public StreamWriter results;
        public string resultsFile = "results.txt";

        private struct Score
        {
            public Int64 timeOfLife;
            public int winCount;
            public int[] DNA;

            public Score(Int64 timeOfLife, int winCount, int[] DNA)
            {
                this.timeOfLife = timeOfLife;
                this.winCount = winCount;
                this.DNA = DNA;
            }

            public override string ToString()
            {
                string ret = timeOfLife + " " + winCount;
                foreach (int i in DNA)
                {
                    ret += " \t" + i;
                }
                return ret;
            }
        }

        public int countAliveHuman()
        {
            int ret = 0;
            foreach (Human human in humans)
            {
                if (!human.dead)
                {
                    ret++;
                }
            }
            return ret;
        }

        public int countAliveZombie()
        {
            int ret = 0;
            foreach (Zombie zombie in zombies)
            {
                if (!zombie.dead)
                {
                    ret++;
                }
            }
            return ret;
        }

        public World()
        {
            codeToColor = new Color[]{
                Color.Transparent,
                new Color(237, 28, 36, 255),    // Red - wall
                new Color(34, 177, 76, 255),    // Green - grass
                new Color(195, 195, 195, 255),  // Grey 25% - stone floor
                new Color(0, 0, 0, 255),        // Black - street
                new Color(181, 230, 29, 255),   // LightGreen - tree
                new Color(185, 122, 87, 255),   // Brown - door
                new Color(127,127,127,255),     // Grey 50% - shelf
            };

            actors = new List<Actor>();
            units = new List<Unit>();
            humans = new List<Human>();
            zombies = new List<Zombie>();

            aiControllers = new List<AIController>();
            time = 0;
            results = new StreamWriter(resultsFile);
        }


        public static float Distance(Actor a, Actor b)
        {
            return (float)Math.Sqrt(Math.Pow(a.position.x - b.position.x, 2) + Math.Pow(a.position.y - b.position.y, 2));
        }

        public void UnSpawn(Unit u)
        {
            AIController ai = u.Controller as AIController;
            if (ai != null)
            {
                aiControllers.Remove(ai);
            }
            else
            {
                PlayerController _pc = u.Controller as PlayerController;
                if (_pc != null)
                {
                    pc.UnPosses();
                }
            }

            u.UnPossesed();


            terrain.takeFrom(u);
            actors.Remove(u);
            units.Remove(u);

            Human human = u as Human;
            if (human != null)
            {
                humans.Remove(human);
                return;
            }
            Zombie zombie = u as Zombie;
            if (zombie != null)
            {
                zombies.Remove(zombie);
                return;
            }
        }

        public void Spawn(Controller controller, Unit unit)
        {
            Spawn(unit);

            AIController ai = controller as AIController;
            if (ai != null)
            {
               aiControllers.Add(ai);
            }
            else
            {
                PlayerController _pc = controller as PlayerController;
                if (_pc != null)
                {
                    pc = _pc;
                }
            }

            controller.Posses(unit);
        }


        public bool Spawn(Actor newActor)
        {
            terrain.putOn(newActor);
            actors.Add(newActor);

            Unit unit = newActor as Unit;
            if (unit != null)
            {
                units.Add(unit);

                Human human = unit as Human;
                if (human != null)
                {
                    humans.Add(human);
                    return true;
                }
                Zombie zombie = unit as Zombie;
                if (zombie != null)
                {
                    zombies.Add(zombie);
                    return true;
                }
            }

            return true;
        }

        
        public static Vec2 DirToVec2(Direction direction)
        {
            switch (direction)
            {
                case Direction.North:
                    return new Vec2(0, -1);
                case Direction.South:
                    return new Vec2(0, 1);
                case Direction.West:
                    return new Vec2(-1, 0);
                case Direction.East:
                    return new Vec2(1, 0);
                default:
                    return new Vec2(0);
            }
        }


        public void ClearWorld()
        {
            actors.Clear();
            units.Clear();
            humans.Clear();
            zombies.Clear();
            aiControllers.Clear();
            terrain.Clear();


            clear = false;
        }

        public void spawnItems(int N)
        {
            Vec2 v;
            for (int i = 0; i < N; i++)
            {
                do
                {
                    v.x = rand.Next(terrain.X);
                    v.y = rand.Next(terrain.Y);
                } while (terrain.blocking(v) != Result.NoBlock);

                spawnRandomShopItem(terrain.getField(v));                
            }
        }

        private void spawnRandomShopItem(Field field)
        {
            int r = rand.Next(2);
            if (r == 0)
            {
                field.putOn(new Food(field.position.x, field.position.y, this));
            }
            if (r == 1)
            {
                field.putOn(new Weapon(field.position.x, field.position.y, this));
            }
            else
            {
                field.putOn(new Food(field.position.x, field.position.y, this));
            }

        }

        public void spawnHumans(int N)
        {
            int x, y;
            for (int i = 0; i < N; i++)
            {
                do
                {
                    x = rand.Next(terrain.X);
                    y = rand.Next(terrain.Y);
                } while (terrain.blocking(x, y) != Result.NoBlock);

                int[] DNA = {
                                           rand.Next(40) + 1,
                                           rand.Next(40) + 1,
                                           rand.Next(40) + 1,
                                           rand.Next(40) + 1,
                                           rand.Next(40) + 1,
                                           rand.Next(40) + 1,
                                           rand.Next(40) + 1,
                                       };

                Spawn(new FuzzyHumanAI(this, DNA), new Human(x, y, this));
            }
        }

        
        public void spawnHumans(List<int[]> DNA)
        {
            int x, y;
            for (int i = 0; i < DNA.Count; i++)
            {
                do
                {
                    x = rand.Next(terrain.X);
                    y = rand.Next(terrain.Y);
                } while (terrain.blocking(x, y) != Result.NoBlock);

                Spawn(new FuzzyHumanAI(this, DNA[i]), new Human(x, y, this));
            }
        }


        public void spawnZombies(int N)
        {
            int x, y;
            for (int i = 0; i < N; i++)
            {
                do
                {
                    x = rand.Next(terrain.X);
                    y = rand.Next(terrain.Y);
                } while (terrain.blocking(x, y) != Result.NoBlock);

                Spawn(new ZombieAI(this), new Zombie(x, y, this));
            }
        }


        public void nextIteration()
        {
            List<Score> population = new List<Score>(100);
            foreach (Human human in humans)
            {
                population.Add(new Score(((FuzzyHumanAI)human.Controller).age, human.winCount, ((FuzzyHumanAI)human.Controller).mDNA));
            }

            if (population.Count <= 0)
            {
                ClearWorld();
                spawnItems(400);
                spawnHumans(100);
                spawnZombies(150);
                iteration++;
                return;
            }

            List<Score> best = new List<Score>(10);
            for (int i = 0; i < takeBest; i++)
            {
                int bestIndex = 0;
                for (int j = 1; j < population.Count; j++)
                {
                    if //(population[j].timeOfLife > population[bestIndex].timeOfLife || (population[j].timeOfLife == population[bestIndex].timeOfLife && population[j].winCount > population[bestIndex].winCount))
                        (population[j].winCount > population[bestIndex].winCount || (population[j].winCount == population[bestIndex].winCount && population[j].timeOfLife > population[bestIndex].timeOfLife))
                    {
                        bestIndex = j;
                    }
                }

                best.Add(population[bestIndex]);
                population.RemoveAt(bestIndex);
            }

            Raport(best);

            List<int[]> newPopulation = generateNewPopulation(best);

            ClearWorld();
            spawnItems(400);

            


            spawnHumans(newPopulation);
            spawnZombies(150);

            iteration++;
        }

        private void Raport(List<Score> best)
        {
            results.WriteLine();
            results.WriteLine("#########################");
            results.WriteLine("Wyniki iteracji nr " + iteration + ".");
            results.WriteLine("Ludzie: " + countAliveHuman() + " Zombie: " + countAliveZombie());
            results.WriteLine("Czas trwania iteracji: " + (time - lastIterationTime) + " tur.");
            results.WriteLine("Najlepsze DNA:");
            foreach (Score score in best)
            {
                results.WriteLine(score);
            }
            results.WriteLine();

            results.Flush();
        }

        private List<int[]> generateNewPopulation(List<Score> best)
        {
            List<int[]> ret = new List<int[]>(100);
            

            // From last population
            for(int i  = 0; i<best.Count-1; i++)
                for (int j = i + 1; j < best.Count; j++)
                {
                    ret.Add(xmutate(xcross(best[i].DNA, best[j].DNA)));
                    ret.Add(xmutate(xcross(best[i].DNA, best[j].DNA)));
                }


            // From migration
            for (int i = 0; i < 10; i++)
            {
                int[] randomDNA = {
                    rand.Next(40) + 1,
                    rand.Next(40) + 1,
                    rand.Next(40) + 1,
                    rand.Next(40) + 1,
                    rand.Next(40) + 1,
                    rand.Next(40) + 1,
                    rand.Next(40) + 1,
                };

                ret.Add(randomDNA);
            }


            return ret;
        }

        public int[] xmutate(int[] a)
        {
            // Mutation chance is 1%
            if (rand.Next(100) > 0)
            {
                return a;
            }

            int r = rand.Next(a.Length);
            int sign = rand.Next(2);
            int value = rand.Next(10)+1;

            if (sign == 0)
            {
                a[r] -= value;
                if (a[r] < 1) a[r] = 1;
            }
            else
            {
                a[r] += value;
            }

            return a;
        }

        public int[] xcross(int[] a, int[] b)
        {
            Debug.Assert(a.Length == b.Length,"xcross: 'a' and 'b' have diffrent lengths");

            int[] ret = new int[a.Length];

            for (int i = 0; i < ret.Length; i++)
            {
                if (rand.Next(2) == 0)
                {
                    ret[i] = a[i];
                }
                else
                {
                    ret[i] = b[i];
                }
            }
            return ret;
        }


        private void migrateZombie()
        {
            int x, y;

            do
            {
                x = rand.Next(terrain.X);
                y = rand.Next(terrain.Y);
            } while (terrain.blocking(x, y) != Result.NoBlock);


            Spawn(new ZombieAI(this), new Zombie(x, y, this));
        }


        public void endTurn()
        {
            if (iteration>0 && countAliveZombie() < 50)
            {
                migrateZombie();
            }

            foreach (AIController ai in aiControllers)
            {
                ai.newTurn();
            }
            pc.newTurn();
            
            time++;
        }
    }
}
